<!doctype html><html lang=zh-cn><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>一文看懂JVM内存布局及GC原理</title><link rel=stylesheet href=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/notes.css media=all><script src=/static/main.js></script><body data-category=default data-clipid=1567134506><div class="mx-wc-main yue"><div><div><div><div><div><div><p>“java的内存布局以及GC原理”是java开发人员绕不开的话题，也是面试中常见的高频问题之一。<p><br><p>java发展历史上出现过很多垃圾回收器，各有各的适应场景，很多网上的旧文章已经跟不上最新的变化。本文详细介绍了java的内存布局以及各种垃圾回收器的原理（包括最新的ZGC），希望阅读完后，大家对这方面的知识不再陌生，有所收获，同时也欢迎大家留言讨论。<br><p><br><section><section><section><section><section><p><strong>一、JVM运行时内存布局</strong></section></section></section></section><section><section><br></section></section></section><p><br><p>按<a href=https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5>java 8虚拟机规范</a>的原始表达：(jvm)Run-Time Data Areas, 暂时翻译为“jvm运行时内存布局”。<p><br><p>从概念上大致分为6个（逻辑）区域，参考下图。注：Method Area中还有一个常量池区，图中未明确标出。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/74b4c2d8 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNNzzf7ctmmww89DsxrhRZAmZuhqy0zcN5q9YC48mngmCiabo3JibNYRgA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>这6块区域按是否被线程共享，可以分为两大类：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/c67c5dc8 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNicp7p1V0HcuiaCiczGNq1TickYqj3p2SGZ8icCVGpd2BFYns4VoIHq77CFw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>一类是每个线程所独享的：<br><p><br><p>1）PC Register：也称为程序计数器， 记录每个线程当前执行的指令信。eg：当前执行到哪一条指令，下一条该取哪条指令。<p><br><p>2）JVM Stack：也称为虚拟机栈，记录每个栈帧（Frame）中的局部变量、方法返回地址等。注：这里出现了一个新名词“<a href=https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6>栈帧</a>”，它的结构如下：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/c210878e data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN4H8Ca4noz1z9vpr01V9QtJWC6UNeukBk52xK0lo4USvIXHjRVhiaq7Q/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>线程中每次有方法调用时，会创建Frame，方法调用结束时Frame销毁。<br><p><br><p>3）Native Method Stack：本地(原生)方法栈，顾名思义就是调用操作系统原生本地方法时，所需要的内存区域。<p><br><p>上述3类区域，生命周期与Thread相同，即：线程创建时，相应的区域分配内存，线程销毁时，释放相应内存。<p> <p>另一类是所有线程共享的：<p><br><p>1）Heap：即鼎鼎大名的堆内存区，也是GC垃圾回收的主站场，用于存放类的实例对象及Arrays实例等。<p><br><p>2）Method Area：方法区，主要存放类结构、类成员定义，static静态成员等。<p><br><p>3）Runtime Constant Pool：运行时常量池，比如：字符串，int -128~127范围的值等，它是Method Area中的一部分。<p><br><p>Heap、Method Area 都是在虚拟机启动时创建，虚拟机退出时释放。<p><br><p>注：Method Area 区，虚拟机规范只是说必须要有，但是具体怎么实现（比如:是否需要垃圾回收? ），交给具体的JVM实现去决定，逻辑上讲，视为Heap区的一部分。所以，如果你看见类似下面的图，也不要觉得画错了。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/482cc667 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN1msX6XzoDkBz1alSDF0OOQbyc9Wg0m7WqdqJGl3M5ucg7PHsMgNGAg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>上述6个区域，除了PC Register区不会抛出StackOverflowError或OutOfMemoryError ，其它5个区域，当请求分配的内存不足时，均会抛出OutOfMemoryError（即：OOM），其中thread独立的JVM Stack区及Native Method Stack区还会抛出StackOverflowError。<p> <p>最后，还有一类不受JVM虚拟机管控的内存区，这里也提一下，即：堆外内存。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/d88d3ae6 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNdlZIgRR1Q5jUy79as4eDZr50B0aSSZpcYAhAOfIMt8QXj7714AhH1A/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>可以通过Unsafe和NIO包下的DirectByteBuffer来操作堆外内存。如上图，虽然堆外内存不受JVM管控，但是堆内存中会持有对它的引用，以便进行GC。<br><p><br>提一个问题：总体来看，JVM把内存划分为“栈(stack)”与“堆(heap)”两大类，为何要这样设计？<p><br><p>个人理解，程序运行时，内存中的信息大致分为两类，一是跟程序执行逻辑相关的指令数据，这类数据通常不大，而且生命周期短；一是跟对象实例相关的数据，这类数据可能会很大，而且可以被多个线程长时间内反复共用，比如字符串常量、缓存对象这类。<p><br><p>将这两类特点不同的数据分开管理，体现了软件设计上“模块隔离”的思想。好比我们通常会把后端service与前端website解耦类似，也更便于内存管理。<p> <section><section><section><section><section><p><strong>二、GC垃圾回收原理</strong></section></section></section></section><section><section><br></section></section></section><p><br><section><section><section><span></span><span></span> </section><section><p><strong>2.1 如何判断对象是垃圾 ?</strong> </section></section></section><p><br><p>有两种经典的判断方法，借用网友的图（文中最后有给出链接）：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/de4a5a89 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNXZic6ot5LtRyn778DxwzknOGk5GVm5vmruvkIy6ujOPXh8j4CcaibU5Q/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>引用计数法，思路很简单，但是如果出现循环引用，即：A引用B，B又引用A，这种情况下就不好办了，所以JVM中使用了另一种称为“可达性分析”的判断方法：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/ccb5d7bd data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNrGKpiaVTt1h3nicJquxrOXGeZrwr8tpxptzz0MTGmWgRICApIMRhVumA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>还是刚才的循环引用问题（也是某些公司面试官可能会问到的问题)，如果A引用B，B又引用A，这2个对象是否能被GC回收？ <p><br><p>答案：关键不是在于A、B之间是否有引用，而是A、B是否可以一直向上追溯到GC Roots。如果与GC Roots没有关联，则会被回收，否则将继续存活。<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/53554c8f data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNM1HvBAPEp5z9MiaF2MVHBnUyjH2JpELQ3kdNibfafOyoQjicV5OZal8sQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>上图是一个用“可达性分析”标记垃圾对象的示例图，灰色的对象表示不可达对象，将等待回收。<p> <section><section><section><span></span><span></span> </section><section><p><strong>2.2 哪些内存区域需要GC ?</strong></section></section></section><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/db0aa807 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNzia5Fa7Fh34EbF0ecwJadw5Ej0ibo0yv0icOLFw4ziclCFnTibkPraxajHg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>在第一部分JVM内存布局中，我们知道了thread独享的区域：PC Regiester、JVM Stack、Native Method Stack，其生命周期都与线程相同（即：与线程共生死），所以无需GC。线程共享的Heap区、Method Area则是GC关注的重点对象。<p> <section><section><section><span></span><span></span> </section><section><p><strong>2.3 常用的GC算法</strong></section></section></section><p><br><p>1）mark-sweep 标记清除法<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/3bad0b47 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNv7ibl41rxiaF69pHI8rNcItribR10icTMaYc6TxYc5AIcb8H89adP0g82A/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>如上图，黑色区域表示待清理的垃圾对象，标记出来后直接清空。该方法简单快速，但是缺点也很明显，会产生很多内存碎片。<p> <p>2）mark-copy 标记复制法<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/a55d0351 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNXHicLNMHN3VDA75yxO1RvicflJiaZq6jqgmEVJ9bSWVnbZXba1WTURCBA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>思路也很简单，将内存对半分，总是保留一块空着（上图中的右侧），将左侧存活的对象（浅灰色区域）复制到右侧，然后左侧全部清空。避免了内存碎片问题，但是内存浪费很严重，相当于只能使用50%的内存。<br><p> <p>3）mark-compact 标记-整理（也称标记-压缩）法<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/45075da1 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNqicjZ5PunOGibia6XKr7RM6hcaxu2rDFpzwvd6H7osa5WX0DdZzjVI3Lg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>避免了上述两种算法的缺点，将垃圾对象清理掉后，同时将剩下的存活对象进行整理挪动（类似于windows的磁盘碎片整理），保证它们占用的空间连续，这样就避免了内存碎片问题，但是整理过程也会降低GC的效率。<br><p> <p>4）generation-collect 分代收集算法<p><br><p>上述三种算法，每种都有各自的优缺点，都不完美。在现代JVM中，往往是综合使用的，经过大量实际分析，发现内存中的对象，大致可以分为两类：有些生命周期很短，比如一些局部变量/临时对象，而另一些则会存活很久，典型的比如websocket长连接中的connection对象，如下图：<p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/4eb573b0 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNaweITVkwR0JufL6ibAbbAZiaqxlVpI2UJSMPJmMpMibcI8tBDpPMKf4ug/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p>纵向y轴可以理解分配内存的字节数，横向x轴理解为随着时间流逝（伴随着GC），可以发现大部分对象其实相当短命，很少有对象能在GC后活下来。因此诞生了分代的思想，以Hotspot为例（JDK 7）：<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/4e271fc0 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNSDfTIyVLe2PuuHQ9eXKRNJqgty5HK9y00cerZmeuPmNLQvukG84MCQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>将内存分成了三大块：年青代（Young Genaration），老年代（Old Generation）,永久代（Permanent Generation），其中Young Genaration更是又细为分eden，S0，S1三个区。<p><br><p>结合我们经常使用的一些jvm调优参数后，一些参数能影响的各区域内存大小值，示意图如下：<p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/c92614ad data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN0fUQQubYNiaCelGWvQebzd5ibqCh1utSpwLK7233vgHwcnOqphhakqbg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p>注：jdk8开始，用MetaSpace区取代了Perm区（永久代），所以相应的jvm参数变成-XX:MetaspaceSize 及 -XX:MaxMetaspaceSize。<br><p><br><p>以Hotspot为例，我们来分析下GC的主要过程：<p><br><p>刚开始时，对象分配在eden区，s0（即：from）及s1（即：to）区，几乎是空着。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/a5aed616 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNQlSN3MbIOtplibp4HvMERRMkkofh2ysbolkuJk9j4CGCaAsRQ4rwm3Q/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>随着应用的运行，越来越多的对象被分配到eden区。<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/0dc33af1 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNoaqTW1iapNPMBOF6KnAdR0XFO8FAolVevuwqX2kMedMmtCfJ3q36Jicg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>当eden区放不下时，就会发生minor GC（也被称为young GC），第1步当然是要先标识出不可达垃圾对象（即：下图中的黄色块），然后将可达对象，移动到s0区（即：4个淡蓝色的方块挪到s0区），然后将黄色的垃圾块清理掉，这一轮过后，eden区就成空的了。<p><br><p>注：这里其实已经综合运用了“【标记-清理eden】 + 【标记-复制 eden-&gt;s0】”算法。<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/eef5006c data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN7TsCfNA6JpIMyzUNXF4EfmZJGuI54O220ILEL0BicduDXs3UfMt2QhQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>随着时间推移，eden如果又满了，再次触发minor GC，同样还是先做标记，这时eden和s0区可能都有垃圾对象了（下图中的黄色块），注意：这时s1（即：to）区是空的，s0区和eden区的存活对象，将直接搬到s1区。然后将eden和s0区的垃圾清理掉，这一轮minor GC后，eden和s0区就变成了空的了。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/34a5852a data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNyoA3YSqmZwzNFAVENX1k8Yoib7dzAs8OTKOlqXp4G5woX7Oe4Q2FLgg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>继续，随着对象的不断分配，eden空可能又满了，这时会重复刚才的minor GC过程，不过要注意的是，这时候s0是空的，所以s0与s1的角色其实会互换，即：存活的对象，会从eden和s1区，向s0区移动。然后再把eden和s1区中的垃圾清除，这一轮完成后，eden与s1区变成空的，如下图。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/b2dbad28 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN1FVNWibxGobL9J2KN2Vk9wUiav5W1In2FA1V2Jp8AGibo3NzohUZzAgxA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>对于那些比较“长寿”的对象一直在s0与s1中挪来挪去，一来很占地方，而且也会造成一定开销，降低gc效率，于是有了“代龄(age)”及“晋升”。<p><br><p>对象在年青代的3个区(edge,s0,s1)之间，每次从1个区移到另1区，年龄+1，在young区达到一定的年龄阈值后，将晋升到老年代。下图中是8，即：挪动8次后，如果还活着，下次minor GC时，将移动到Tenured区。<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/10bccc7c data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNInO9zXqlvjCxQceum5d3OrRloly3sjiccibKq18EYnQ6BTUsYMibIJwIA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p> <p>下图是晋升的主要过程：对象先分配在年青代，经过多次Young GC后，如果对象还活着，晋升到老年代。<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/7301a622 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNo3Pp70qzP9sSq5yzv1tiaaINIWcetSFrD6ZUpFbag1ahZPicKlGfzOkA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>如果老年代，最终也放满了，就会发生major GC（即Full GC），由于老年代的的对象通常会比较多，因为标记-清理-整理（压缩）的耗时通常会比较长，会让应用出现卡顿的现象，这也是为什么很多应用要优化，尽量避免或减少Full GC的原因。<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/2daebe94 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNbvQX1EjIBSDicFZ6oEZ7DolE6tK1Ba2Xuu03uia5BAcUzZOCYdmBfdpw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>注：上面的过程主要来自oracle官网的资料，但是有一个细节官网没有提到，如果分配的新对象比较大，eden区放不下，但是old区可以放下时，会直接分配到old区（即没有晋升这一过程，直接到老年代了）。<br><p> <p>下图引自阿里出品的《码出高效-Java开发手册》一书，梳理了GC的主要过程。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/f852cfc4 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNiaEhW3YkAqlqoI6nTtQB8z8DHjALicDoy9cTicUBVyUxiaqyWb0DtibVj5w/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p>  <section><section><section><section><section><p><strong>三、垃圾回收器</strong></section></section></section></section><section><section><br></section></section></section><p><br><p>不算最新出现的神器ZGC，历史上出现过7种经典的垃圾回收器。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/db10ddc2 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNxPODp0hCcd4r8T34licRQmas45qQKm94357mRj303KicmyfYUzt5Ut2A/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>这些回收器都是基于分代的，把G1除外，按回收的分代划分，横线以上的3种：Serial ,ParNew, Parellel Scavenge都是回收年青代的，横线以下的3种：CMS，Serial Old, Parallel Old 都是回收老年代的。<p><br><section><section><section><span></span><span></span> </section><section><p><strong>3.1 Serial 收集器</strong></section></section></section><p><br><p>单线程用标记-复制算法，快刀斩乱麻，单线程的好处避免上下文切换，早期的机器，大多是单核，也比较实用。但执行期间，会发生STW（Stop The World）。<p><br><section><section><section><span></span><span></span> </section><section><p><strong>3.2 ParNew 收集器</strong></section></section></section><p><br><p>Serial的多线程版本，同样会STW，在多核机器上会更适用。<p><br><section><section><section><span></span><span></span> </section><section><p><strong>3.3 Parallel Scavenge 收集器</strong></section></section></section><p><br><p>ParNew的升级版本，主要区别在于提供了两个参数：-XX:MaxGCPauseMillis 最大垃圾回收停顿时间；-XX:GCTimeRatio 垃圾回收时间与总时间占比，通过这2个参数，可以适当控制回收的节奏，更关注于吞吐率，即总时间与垃圾回收时间的比例。<p><br><section><section><section><span></span><span></span> </section><section><p><strong>3.4 Serial Old 收集器</strong></section></section></section><p><br><p>因为老年代的对象通常比较多，占用的空间通常也会更大，如果采用复制算法，得留50%的空间用于复制，相当不划算，而且因为对象多，从1个区，复制到另1个区，耗时也会比较长，所以老年代的收集，通常会采用“标记-整理”法。从名字就可以看出来，这是单线程（串行）的， 依然会有STW。<p><br><section><section><section><span></span><span></span> </section><section><p><strong>3.5 Parallel Old 收集器</strong></section></section></section><p><br><p>一句话：Serial Old的多线程版本。<p><br><section><section><section><span></span><span></span> </section><section><p><strong>3.6 CMS 收集器</strong></section></section></section><p><br><p>全称：Concurrent Mark Sweep，从名字上看，就能猜出它是并发多线程的。这是JDK 7中广泛使用的收集器，有必要多说一下，借一张网友的图说话：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/a3044447 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNgybzaWaibS3BZv1Yl1F9UFaATjgvCjy6FrIsTLk1ffhCribb7iaYHIBaw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>相对3.4 Serial Old收集器或3.5 Parallel Old收集器而言，这个明显要复杂多了，分为4个阶段：<p><br><p>1）Inital Mark 初始标记：主要是标记GC Root开始的下级（注：仅下一级）对象，这个过程会STW，但是跟GC Root直接关联的下级对象不会很多，因此这个过程其实很快。<p><br><p>2）Concurrent Mark 并发标记：根据上一步的结果，继续向下标识所有关联的对象，直到这条链上的最尽头。这个过程是多线程的，虽然耗时理论上会比较长，但是其它工作线程并不会阻塞，没有STW。<p><br><p>3）Remark 再标志：为啥还要再标记一次？因为第2步并没有阻塞其它工作线程，其它线程在标识过程中，很有可能会产生新的垃圾。<p><br><p>试想下，高铁上的垃圾清理员，从车厢一头开始吆喝“有需要扔垃圾的乘客，请把垃圾扔一下”，一边工作一边向前走，等走到车厢另一头时，刚才走过的位置上，可能又有乘客产生了新的空瓶垃圾。所以，要完全把这个车厢清理干净的话，她应该喊一下：所有乘客不要再扔垃圾了（STW），然后把新产生的垃圾收走。当然，因为刚才已经把收过一遍垃圾，所以这次收集新产生的垃圾，用不了多长时间（即：STW时间不会很长）。<p><br><p>4）Concurrent Sweep：并行清理，这里使用多线程以“Mark Sweep-标记清理”算法，把垃圾清掉，其它工作线程仍然能继续支行，不会造成卡顿。<p><br><p>等等，刚才我们不是提到过“标记清理”法，会留下很多内存碎片吗？确实，但是也没办法，如果换成“Mark Compact标记-整理”法，把垃圾清理后，剩下的对象也顺便排整理，会导致这些对象的内存地址发生变化，别忘了，此时其它线程还在工作，如果引用的对象地址变了，就天下大乱了。<p><br><p>另外，由于这一步是并行处理，并不阻塞其它线程，所以还有一个副使用，在清理的过程中，仍然可能会有新垃圾对象产生，只能等到下一轮GC，才会被清理掉。<p><br><p>虽然仍不完美，但是从这4步的处理过程来看，以往收集器中最让人诟病的长时间STW，通过上述设计，被分解成二次短暂的STW，所以从总体效果上看，应用在GC期间卡顿的情况会大大改善，这也是CMS一度十分流行的重要原因。<p><br><section><section><section><span></span><span></span> </section><section><p><strong>3.7 G1 收集器</strong></section></section></section><p><br><p>G1的全称是Garbage-First，为什么叫这个名字，呆会儿会详细说明。鉴于CMS的一些不足之外，比如: 老年代内存碎片化，STW时间虽然已经改善了很多，但是仍然有提升空间。G1就横空出世了，它对于heap区的内存划思路很新颖，有点算法中分治法“分而治之”的味道。<p><br><p>如下图，G1将heap内存区，划分为一个个大小相等（1-32M，2的n次方）、内存连续的Region区域，每个region都对应Eden、Survivor 、Old、Humongous四种角色之一，但是region与region之间不要求连续。<p><br><p>注：Humongous，简称H区是专用于存放超大对象的区域，通常&gt;= 1/2 Region Size，且只有Full GC阶段，才会回收H区，避免了频繁扫描、复制/移动大对象。<p><br><p>所有的垃圾回收，都是基于1个个region的。JVM内部知道，哪些region的对象最少（即：该区域最空），总是会优先收集这些region（因为对象少，内存相对较空，肯定快），这也是Garbage-First得名的由来，G即是Garbage的缩写， 1即First。<p> <p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/5a3a4e24 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNSrlvf8BAdR8UCc71Q5ibQstCsrhcJ2Qiac0oRbh1FMIl9f7PvBZPFjVQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p><strong>G1 Young GC</strong><p><br><p>young GC前：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/e45274c7 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNj9IA5RDHUftNGErK2EGbmvguCHXGwowYydNh1950VJ1ia9b79uU2Rsw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p>young GC后：<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/95450902 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNDkmaVySowrwGsHBkTiaXaBMIHD8PKLYM1TMTZDXUHn1FY4CPicOWg64Q/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>理论上讲，只要有一个Empty Region（空区域），就可以进行垃圾回收。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/70ecbecf data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNWDxs6Gfe6wTceKI9VCUBDJYPA4gBKDHkJrjNPyFcp6MLdiaqDnXOAgg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>由于region与region之间并不要求连续，而使用G1的场景通常是大内存，比如64G甚至更大，为了提高扫描根对象和标记的效率，G1使用了二个新的辅助存储结构：<p><br><p>Remembered Sets：简称RSets，用于根据每个region里的对象，是从哪指向过来的（即：谁引用了我），每个Region都有独立的RSets。（Other Region -&gt; Self Region）。<p><br><p>Collection Sets ：简称CSets，记录了等待回收的Region集合，GC时这些Region中的对象会被回收（copied or moved）。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/aa26e080 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNSWnSVv5InmHqicOjCE1jZ0dcPzBsAkQgEQ6BFTC40H3CT0MHFPOfGVA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>RSets的引入，在YGC时，将年青代Region的RSets做为根对象，可以避免扫描老年代的region，能大大减轻GC的负担。注：在老年代收集Mixed GC时，RSets记录了Old-&gt;Old的引用，也可以避免扫描所有Old区。<p> <p><strong>Old Generation Collection（也称为 Mixed GC）</strong><p><br><p>按oracle官网文档描述分为5个阶段：Initial Mark(STW) -&gt; Root Region Scan -&gt; Cocurrent Marking -&gt; Remark(STW) -&gt; Copying/Cleanup(STW &amp;&amp; Concurrent)<p><br><p>注：也有很多文章会把Root Region Scan省略掉，合并到Initial Mark里，变成4个阶段。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/7b621d81 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNZcxgicEwrx5L0yooWYezJMTox64T6Wg8gzd4X4PHrGHLKmdsPsr0EgQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>存活对象的“初始标记”依赖于Young GC，GC 日志中会记录成young字样。<br><p><br><section><pre class=code-snippet__js><code class="hljs css">2019<span class=hljs-selector-tag>-06-09T15</span><span class=hljs-selector-pseudo>:24</span><span class=hljs-selector-pseudo>:37.086+0800</span>: 500993<span class=hljs-selector-class>.392</span>: <span class=hljs-selector-attr>[GC pause (G1 Evacuation Pause) (young), 0.0493588 secs]</span></code><code class="hljs json">   [Parallel Time: <span class=hljs-number>41.9</span> ms, GC Workers: <span class=hljs-number>8</span>]</code><code class="hljs json">      [GC Worker Start (ms): Min: <span class=hljs-number>500993393.7</span>, Avg: <span class=hljs-number>500993393.7</span>, Max: <span class=hljs-number>500993393.7</span>, Diff: <span class=hljs-number>0.1</span>]</code><code class="hljs json">      [Ext Root Scanning (ms): Min: <span class=hljs-number>1.5</span>, Avg: <span class=hljs-number>2.2</span>, Max: <span class=hljs-number>4.4</span>, Diff: <span class=hljs-number>2.8</span>, Sum: <span class=hljs-number>17.2</span>]</code><code class="hljs json">      [Update RS (ms): Min: <span class=hljs-number>15.8</span>, Avg: <span class=hljs-number>18.1</span>, Max: <span class=hljs-number>18.9</span>, Diff: <span class=hljs-number>3.1</span>, Sum: <span class=hljs-number>144.8</span>]</code><code class="hljs json">         [Processed Buffers: Min: <span class=hljs-number>110</span>, Avg: <span class=hljs-number>144.9</span>, Max: <span class=hljs-number>163</span>, Diff: <span class=hljs-number>53</span>, Sum: <span class=hljs-number>1159</span>]</code><code class="hljs json">      [Scan RS (ms): Min: <span class=hljs-number>4.7</span>, Avg: <span class=hljs-number>5.0</span>, Max: <span class=hljs-number>5.1</span>, Diff: <span class=hljs-number>0.4</span>, Sum: <span class=hljs-number>39.7</span>]</code><code class="hljs json">      [Code Root Scanning (ms): Min: <span class=hljs-number>0.0</span>, Avg: <span class=hljs-number>0.0</span>, Max: <span class=hljs-number>0.0</span>, Diff: <span class=hljs-number>0.0</span>, Sum: <span class=hljs-number>0.0</span>]</code><code class="hljs json">      [Object Copy (ms): Min: <span class=hljs-number>16.4</span>, Avg: <span class=hljs-number>16.5</span>, Max: <span class=hljs-number>16.6</span>, Diff: <span class=hljs-number>0.2</span>, Sum: <span class=hljs-number>132.0</span>]</code><code class="hljs json">      [Termination (ms): Min: <span class=hljs-number>0.0</span>, Avg: <span class=hljs-number>0.0</span>, Max: <span class=hljs-number>0.0</span>, Diff: <span class=hljs-number>0.0</span>, Sum: <span class=hljs-number>0.0</span>]</code><code class="hljs json">         [Termination Attempts: Min: <span class=hljs-number>1</span>, Avg: <span class=hljs-number>4.9</span>, Max: <span class=hljs-number>7</span>, Diff: <span class=hljs-number>6</span>, Sum: <span class=hljs-number>39</span>]</code><code class="hljs json">      [GC Worker Other (ms): Min: <span class=hljs-number>0.0</span>, Avg: <span class=hljs-number>0.0</span>, Max: <span class=hljs-number>0.1</span>, Diff: <span class=hljs-number>0.1</span>, Sum: <span class=hljs-number>0.3</span>]</code><code class="hljs json">      [GC Worker Total (ms): Min: <span class=hljs-number>41.7</span>, Avg: <span class=hljs-number>41.8</span>, Max: <span class=hljs-number>41.8</span>, Diff: <span class=hljs-number>0.1</span>, Sum: <span class=hljs-number>334.1</span>]</code><code class="hljs json">      [GC Worker End (ms): Min: <span class=hljs-number>500993435.5</span>, Avg: <span class=hljs-number>500993435.5</span>, Max: <span class=hljs-number>500993435.5</span>, Diff: <span class=hljs-number>0.0</span>]</code><code class="hljs json">   [Code Root Fixup: <span class=hljs-number>0.0</span> ms]</code><code class="hljs json">   [Code Root Purge: <span class=hljs-number>0.0</span> ms]</code><code class="hljs json">   [Clear CT: <span class=hljs-number>0.2</span> ms]</code><code class="hljs json">   [Other: <span class=hljs-number>7.2</span> ms]</code><code class="hljs json">      [Choose CSet: <span class=hljs-number>0.0</span> ms]</code><code class="hljs json">      [Ref Proc: <span class=hljs-number>4.3</span> ms]</code><code class="hljs json">      [Ref Enq: <span class=hljs-number>0.1</span> ms]</code><code class="hljs json">      [Redirty Cards: <span class=hljs-number>0.1</span> ms]</code><code class="hljs json">      [Humongous Register: <span class=hljs-number>0.1</span> ms]</code><code class="hljs json">      [Humongous Reclaim: <span class=hljs-number>0.1</span> ms]</code><code class="hljs json">      [Free CSet: <span class=hljs-number>0.6</span> ms]</code><code class="hljs rust">   [Eden: <span class=hljs-number>1340.0</span>M(<span class=hljs-number>1340.0</span>M)-&gt;<span class=hljs-number>0.0</span>B(<span class=hljs-number>548.0</span>M) Survivors: <span class=hljs-number>40.0</span>M-&gt;<span class=hljs-number>64.0</span>M Heap: <span class=hljs-number>2868.2</span>M(<span class=hljs-number>12.0</span>G)-&gt;<span class=hljs-number>1499.8</span>M(<span class=hljs-number>12.0</span>G)]</code><code class="hljs json"> [Times: user=<span class=hljs-number>0.35</span> sys=<span class=hljs-number>0.00</span>, real=<span class=hljs-number>0.05</span> secs]</code></pre></section><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/0b12b728 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNybFxacyk4cAYHg17sGtJgXp7kocH1icJpib2fZk4ZhPIEYakB9RGHa0w/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p> <p>并发标记过程中，如果发现某些region全是空的，会被直接清除。<br><p> <p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/3b4dd246 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNKHxsibHewtq05jdrKFK3W4nI95A4rC5U0vENvVTD5dVib9XCCCHfpk3Q/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>进入重新标记阶段。<p> <p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/8b20c764 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNnWN40cCzOQYYzZkkhwgRYBKx2Gg2aic8Myw5D800oagV2MlftdE8hibg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>并发复制/清查阶段。这个阶段，Young区和Old区的对象有可能会被同时清理。GC日志中，会记录为mixed字段，这也是G1的老年代收集，也称为Mixed GC的原因。 <p><br><section><pre class=code-snippet__js><code class="hljs css">2019<span class=hljs-selector-tag>-06-09T15</span><span class=hljs-selector-pseudo>:24</span><span class=hljs-selector-pseudo>:23.959+0800</span>: 500980<span class=hljs-selector-class>.265</span>: <span class=hljs-selector-attr>[GC pause (G1 Evacuation Pause) (mixed), 0.0885388 secs]</span></code><code class="hljs json">   [Parallel Time: <span class=hljs-number>74.2</span> ms, GC Workers: <span class=hljs-number>8</span>]</code><code class="hljs json">      [GC Worker Start (ms): Min: <span class=hljs-number>500980270.6</span>, Avg: <span class=hljs-number>500980270.6</span>, Max: <span class=hljs-number>500980270.6</span>, Diff: <span class=hljs-number>0.1</span>]</code><code class="hljs json">      [Ext Root Scanning (ms): Min: <span class=hljs-number>1.7</span>, Avg: <span class=hljs-number>2.2</span>, Max: <span class=hljs-number>4.1</span>, Diff: <span class=hljs-number>2.4</span>, Sum: <span class=hljs-number>17.3</span>]</code><code class="hljs json">      [Update RS (ms): Min: <span class=hljs-number>11.7</span>, Avg: <span class=hljs-number>13.7</span>, Max: <span class=hljs-number>14.3</span>, Diff: <span class=hljs-number>2.6</span>, Sum: <span class=hljs-number>109.8</span>]</code><code class="hljs json">         [Processed Buffers: Min: <span class=hljs-number>136</span>, Avg: <span class=hljs-number>141.5</span>, Max: <span class=hljs-number>152</span>, Diff: <span class=hljs-number>16</span>, Sum: <span class=hljs-number>1132</span>]</code><code class="hljs json">      [Scan RS (ms): Min: <span class=hljs-number>42.5</span>, Avg: <span class=hljs-number>42.9</span>, Max: <span class=hljs-number>43.1</span>, Diff: <span class=hljs-number>0.5</span>, Sum: <span class=hljs-number>343.1</span>]</code><code class="hljs json">      [Code Root Scanning (ms): Min: <span class=hljs-number>0.0</span>, Avg: <span class=hljs-number>0.0</span>, Max: <span class=hljs-number>0.0</span>, Diff: <span class=hljs-number>0.0</span>, Sum: <span class=hljs-number>0.1</span>]</code><code class="hljs json">      [Object Copy (ms): Min: <span class=hljs-number>14.9</span>, Avg: <span class=hljs-number>15.2</span>, Max: <span class=hljs-number>15.4</span>, Diff: <span class=hljs-number>0.5</span>, Sum: <span class=hljs-number>121.7</span>]</code><code class="hljs json">      [Termination (ms): Min: <span class=hljs-number>0.0</span>, Avg: <span class=hljs-number>0.0</span>, Max: <span class=hljs-number>0.0</span>, Diff: <span class=hljs-number>0.0</span>, Sum: <span class=hljs-number>0.1</span>]</code><code class="hljs json">         [Termination Attempts: Min: <span class=hljs-number>1</span>, Avg: <span class=hljs-number>8.2</span>, Max: <span class=hljs-number>11</span>, Diff: <span class=hljs-number>10</span>, Sum: <span class=hljs-number>66</span>]</code><code class="hljs json">      [GC Worker Other (ms): Min: <span class=hljs-number>0.0</span>, Avg: <span class=hljs-number>0.0</span>, Max: <span class=hljs-number>0.0</span>, Diff: <span class=hljs-number>0.0</span>, Sum: <span class=hljs-number>0.2</span>]</code><code class="hljs json">      [GC Worker Total (ms): Min: <span class=hljs-number>74.0</span>, Avg: <span class=hljs-number>74.0</span>, Max: <span class=hljs-number>74.1</span>, Diff: <span class=hljs-number>0.1</span>, Sum: <span class=hljs-number>592.3</span>]</code><code class="hljs json">      [GC Worker End (ms): Min: <span class=hljs-number>500980344.6</span>, Avg: <span class=hljs-number>500980344.6</span>, Max: <span class=hljs-number>500980344.6</span>, Diff: <span class=hljs-number>0.0</span>]</code><code class="hljs json">   [Code Root Fixup: <span class=hljs-number>0.0</span> ms]</code><code class="hljs json">   [Code Root Purge: <span class=hljs-number>0.0</span> ms]</code><code class="hljs json">   [Clear CT: <span class=hljs-number>0.5</span> ms]</code><code class="hljs json">   [Other: <span class=hljs-number>13.9</span> ms]</code><code class="hljs json">      [Choose CSet: <span class=hljs-number>4.1</span> ms]</code><code class="hljs json">      [Ref Proc: <span class=hljs-number>1.8</span> ms]</code><code class="hljs json">      [Ref Enq: <span class=hljs-number>0.1</span> ms]</code><code class="hljs json">      [Redirty Cards: <span class=hljs-number>0.2</span> ms]</code><code class="hljs json">      [Humongous Register: <span class=hljs-number>0.1</span> ms]</code><code class="hljs json">      [Humongous Reclaim: <span class=hljs-number>0.1</span> ms]</code><code class="hljs json">      [Free CSet: <span class=hljs-number>5.6</span> ms]</code><code class="hljs rust">   [Eden: <span class=hljs-number>584.0</span>M(<span class=hljs-number>584.0</span>M)-&gt;<span class=hljs-number>0.0</span>B(<span class=hljs-number>576.0</span>M) Survivors: <span class=hljs-number>28.0</span>M-&gt;<span class=hljs-number>36.0</span>M Heap: <span class=hljs-number>4749.3</span>M(<span class=hljs-number>12.0</span>G)-&gt;<span class=hljs-number>2930.0</span>M(<span class=hljs-number>12.0</span>G)]</code><code class="hljs json"> [Times: user=<span class=hljs-number>0.61</span> sys=<span class=hljs-number>0.00</span>, real=<span class=hljs-number>0.09</span> secs]</code></pre></section><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/58f005bc data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN6RamKZU6ayvU7l8jibibFO3O7icVvRLHKibwfJ29BCWeFq6SzDs182kWgQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p> 上图是，老年代收集完后的示意图。<p><br><p>通过这几个阶段的分析，虽然看上去很多阶段仍然会发生STW，但是G1提供了一个预测模型，通过统计方法，根据历史数据来预测本次收集，需要选择多少个Region来回收，尽量满足用户的预期停顿值（-XX:MaxGCPauseMillis参数可指定预期停顿值）。<p><br><p>注：如果Mixed GC仍然效果不理想，跟不上新对象分配内存的需求，会使用Serial Old GC（Full GC）强制收集整个Heap。<p><br><p><strong>小结</strong>：与CMS相比，G1有内存整理过程（标记-压缩），避免了内存碎片；STW时间可控（能预测GC停顿时间）。<p> <section><section><section><span></span><span></span> </section><section><p><strong>3.8 ZGC （截止目前史上最好的GC收集器）</strong></section></section></section><p><br><p>在G1的基础上，做了很多改进（JDK 11开始引入）<p><br><p><strong>3.8.1 动态调整大小的Region</strong><p><br><p>G1中每个Region的大小是固定的，创建和销毁Region，可以动态调整大小，内存使用更高效。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/697e61e1.png data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN9g7sTvdcFdEBaWHlhzibJ5lIVYZfWUw2kCbiaInTeWys3wcCyQ0TZapw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p><strong>3.8.2 不分代，干掉了RSets</strong><br><p><br><p>G1中每个Region需要借助额外的RSets来记录“谁引用了我”，占用了额外的内存空间，每次对象移动时，RSets也需要更新，会产生开销。<p><br><p>注：ZGC没有为止，没有实现分代机制，每次都是并发的对所有region进行回收，不象G1是增量回收，所以用不着RSets。不分代的带来的可能性能下降，会用下面马上提到的Colored Pointer &amp;&amp; Load Barrier来优化。<p><br><p><strong>3.8.3 带颜色的指针 Colored Pointer</strong><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/dbdba4f8 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNYiaCzDcVNt5hrm1XQnvra7TUb8HaricvHabNYXuVuAfaoX5Atq2Wfsxg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>这里的指针类似java中的引用，意为对某块虚拟内存的引用。ZGC采用了64位指针（注：目前只支持linux 64位系统），将42-45这4个bit位置赋予了不同的含义，即所谓的颜色标志位，也换为指针的metadata。<p><br><p>finalizable位：仅finalizer（类比c++中的析构函数）可访问；<br>remap位：指向对象当前（最新）的内存地址，参考下面提到的relocation；<br>marked0 &amp;&amp; marked1 位：用于标志可达对象；<p><br><p>这4个标志位，同一时刻只会有1个位置是1。每当指针对应的内存数据发生变化，比如内存被移动，颜色会发生变化。<p><br><p><strong>3.8.4 读屏障 Load Barrier</strong><p><br><p>传统GC做标记时，为了防止其它线程在标记期间修改对象，通常会简单的STW。而ZGC有了Colored Pointer后，引入了所谓的读屏障，当指针引用的内存正被移动时，指针上的颜色就会变化，ZGC会先把指针更新成最新状态，然后再返回。（大家可以回想下java中的<a href=https://www.cnblogs.com/yjmyzz/p/6994796.html>volatile关键字</a>，有异曲同工之妙），这样仅读取该指针时可能会略有开销，而不用将整个heap STW。<p><br><p><strong>3.8.5 重定位 relocation</strong><br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/26627f21 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNjyWVf5l2fcqKeiafH4HI0PaTiaDFsmiahFcEgico7PYYprlQ52sjCemoibA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>如上图，在标记过程中，先从Roots对象找到了直接关联的下级对象1，2，4。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/ddce1111 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNb9xOOKyBAm9I38bdwZhXyal5fMKFDEStSffsMHrXsfvDCmya7QFtsQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>然后继续向下层标记，找到了5，8对象， 此时已经可以判定 3，6，7为垃圾对象。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/01e36d20 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNicphKM1JYbciaVicrQcExr7KSYrQZLG6QkY142UFIekibgSaIZl9Lk9iaIQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>如果按常规思路，一般会将8从最右侧的Region移动或复制到中间的Region，然后再将中间Region的3干掉，最后再对中间Region做压缩compact整理。但ZGC做得更高明，它直接将4，5复制到了一个空的新Region就完事了，然后中间的2个Region直接废弃，或理解为“释放”，做为下次回收的“新”Region。这样的好处是避免了中间Region的compact整理过程。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/ccef648b data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNTdM3nEjxibBmN7w2F9ialQryRyNIOibia2NHlKuKicHgjGSsZ6XDBdXCGxg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>最后，指针重新调整为正确的指向（即：remap），而且上一阶段的remap与下一阶段的mark是混在一起处理的，相对更高效。<p><br><p>Remap的流程图如下：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/3d393993 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNKxKxUH867N3A5FWBD3HqRtSQsorRPKOonSzB6UGSkkT2Tenh2ZMuxg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p><strong>3.8.6 多重映射 Multi-Mapping<br></strong><p><br><p>这个优化，说实话没完全看懂，只能谈下自己的理解（如果有误，欢迎指正)。虚拟内存与实际物理内存，OS会维护一个映射关系，才能正常使用。如下图：<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/97b58586 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNsjA70EHX9ekPACXedxejjONusJxzxicn43EpQq0mkDGHVn6Hov9wFlw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p>zgc的64位颜色指针，在解除映射关系时，代价较高（需要屏蔽额外的42-45的颜色标志位）。考虑到这4个标志位，同1时刻，只会有1位置成1（如下图），另外finalizable标志位，永远不希望被解除映射绑定（可不用考虑映射问题）。<p><br><p>所以剩下3种颜色的虚拟内存，可以都映射到同1段物理内存。即映射复用，或者更通俗点讲，本来3种不同颜色的指针，哪怕0-41位完全相同，也需要映射到3段不同的物理内存，现在只需要映射到同1段物理内存即可。<br><p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/cbc5be43 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNZQGyCNmLyQDIOMiccW8bcYXKy9jCfUbiaoPqDSHRVjHlQ4iaia3x4wIEkg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/bb0da839 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNvyibF9WQ6gkAVBSlmTXLtZ6uS152OKibicliceAk9DfzO9w8mian9WgAC3Q/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p><strong>3.8.7 支持</strong><a href="https://baike.baidu.com/item/NUMA/6906025?fr=aladdin"><strong>NUMA架构</strong></a><p><br><p>NUMA是一种多核服务器的架构，简单来讲，一个多核服务器（比如2core），每个cpu都有属于自己的存储器，会比访问另一个核的存储器会慢很多（类似于就近访问更快）。<p><br><p>相对之前的GC算法，ZGC首次支持了NUMA架构，申请堆内存时，判断当前线程属是哪个CPU在执行，然后就近申请该CPU能使用的内存。<p><br><p><strong>小结</strong>：革命性的ZGC经过上述一堆优化后，每次GC总体卡顿时间按官方说法&lt;10ms。注：启用zgc，需要设置-XX:+UnlockExperimentalVMOptions -XX:+UseZGC。<p> <section><section><section><section><section><p><strong>四、实战练习</strong></section></section></section></section><section><section><br></section></section></section><p><br><p>前面介绍了一堆理论，最后来做一个小的练习，下面是一段模拟OOM的测试代码，我们在G1、CMS这二种常用垃圾回收器上试验一下。<p><br><section><pre class=code-snippet__js><code class="hljs css"><span class=hljs-selector-tag>import</span> <span class=hljs-selector-tag>sun</span><span class=hljs-selector-class>.misc</span><span class=hljs-selector-class>.Unsafe</span>;</code><code class=hljs> </code><code class="hljs css"><span class=hljs-selector-tag>import</span> <span class=hljs-selector-tag>java</span><span class=hljs-selector-class>.lang</span><span class=hljs-selector-class>.reflect</span><span class=hljs-selector-class>.Field</span>;</code><code class="hljs css"><span class=hljs-selector-tag>import</span> <span class=hljs-selector-tag>java</span><span class=hljs-selector-class>.util</span><span class=hljs-selector-class>.ArrayList</span>;</code><code class="hljs css"><span class=hljs-selector-tag>import</span> <span class=hljs-selector-tag>java</span><span class=hljs-selector-class>.util</span><span class=hljs-selector-class>.List</span>;</code><code class=hljs> </code><code class="hljs java"><span class=hljs-keyword>public</span> <span class=hljs-class><span class=hljs-keyword>class</span> <span class=hljs-title>OOMTest</span> </span>{</code><code class=hljs> </code><code class=hljs> </code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>static</span> <span class=hljs-keyword>void</span> <span class=hljs-title>main</span><span class=hljs-params>(String[] args)</span> </span>{</code><code class="hljs javascript">        OOMTest test = <span class=hljs-keyword>new</span> OOMTest();</code><code class="hljs javascript">        <span class=hljs-comment>//heap区OOM测试     </span></code><code class="hljs javascript">        <span class=hljs-comment>//test.heapOOM();</span></code><code class=hljs> </code><code class="hljs javascript">        <span class=hljs-comment>//虚拟机栈和本地方法栈溢出</span></code><code class="hljs javascript">        <span class=hljs-comment>//test.stackOverflow();</span></code><code class=hljs> </code><code class="hljs javascript">        <span class=hljs-comment>//metaspace OOM测试</span></code><code class="hljs javascript">        <span class=hljs-comment>//test.metaspaceOOM();</span></code><code class=hljs> </code><code class="hljs javascript">        <span class=hljs-comment>//堆外内存 OOM测试</span></code><code class="hljs javascript">        <span class=hljs-comment>//test.directOOM();</span></code><code class=hljs>    }</code><code class=hljs> </code><code class=hljs>    /**</code><code class="hljs markdown"><span class=hljs-bullet>     * </span>heap OOM测试</code><code class=hljs>     */</code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>void</span> <span class=hljs-title>heapOOM</span><span class=hljs-params>()</span> </span>{</code><code class="hljs javascript">        List<oomtest> list = <span class=hljs-keyword>new</span> ArrayList&lt;&gt;();</oomtest></code><code class="hljs lua">        <span class=hljs-keyword>while</span> (<span class=hljs-literal>true</span>) {</code><code class="hljs javascript">            list.add(<span class=hljs-keyword>new</span> OOMTest());</code><code class=hljs>        }</code><code class=hljs>    }</code><code class=hljs> </code><code class=hljs> </code><code class="hljs java">    <span class=hljs-keyword>private</span> <span class=hljs-keyword>int</span> stackLength = <span class=hljs-number>1</span>;</code><code class=hljs> </code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>void</span> <span class=hljs-title>stackLeak</span><span class=hljs-params>()</span> </span>{</code><code class=hljs>        stackLength += 1;</code><code class=hljs>        stackLeak();</code><code class=hljs>    }</code><code class=hljs> </code><code class=hljs>    /**</code><code class="hljs markdown"><span class=hljs-bullet>     * </span>VM Stack / Native method Stack 溢出测试</code><code class=hljs>     */</code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>void</span> <span class=hljs-title>stackOverflow</span><span class=hljs-params>()</span> </span>{</code><code class="hljs javascript">        OOMTest test = <span class=hljs-keyword>new</span> OOMTest();</code><code class="hljs javascript">        <span class=hljs-keyword>try</span> {</code><code class="hljs css">            <span class=hljs-selector-tag>test</span><span class=hljs-selector-class>.stackLeak</span>();</code><code class="hljs javascript">        } <span class=hljs-keyword>catch</span> (Throwable e) {</code><code class="hljs css">            <span class=hljs-selector-tag>System</span><span class=hljs-selector-class>.out</span><span class=hljs-selector-class>.println</span>(&#34;<span class=hljs-selector-tag>stack</span> <span class=hljs-selector-tag>length</span><span class=hljs-selector-pseudo>:&#34;</span> + <span class=hljs-selector-tag>test</span><span class=hljs-selector-class>.stackLength</span>);</code><code class="hljs javascript">            <span class=hljs-keyword>throw</span> e;</code><code class=hljs>        }</code><code class=hljs>    }</code><code class=hljs> </code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>void</span> <span class=hljs-title>genString</span><span class=hljs-params>()</span> </span>{</code><code class="hljs javascript">        List<string> list = <span class=hljs-keyword>new</span> ArrayList&lt;&gt;();</string></code><code class="hljs go">        <span class=hljs-keyword>int</span> i = <span class=hljs-number>0</span>;</code><code class="hljs lua">        <span class=hljs-keyword>while</span> (<span class=hljs-literal>true</span>) {</code><code class="hljs lua">            list.add(<span class=hljs-string>&#34;string-&#34;</span> + i);</code><code class=hljs>            i++;</code><code class=hljs>        }</code><code class=hljs>    }</code><code class=hljs> </code><code class=hljs>    /**</code><code class="hljs markdown"><span class=hljs-bullet>     * </span>metaspace/常量池 OOM测试</code><code class=hljs>     */</code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>void</span> <span class=hljs-title>metaspaceOOM</span><span class=hljs-params>()</span> </span>{</code><code class="hljs javascript">        OOMTest test = <span class=hljs-keyword>new</span> OOMTest();</code><code class="hljs css">        <span class=hljs-selector-tag>test</span><span class=hljs-selector-class>.metaspaceOOM</span>();</code><code class=hljs>    }</code><code class=hljs> </code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>void</span> <span class=hljs-title>allocDirectMemory</span><span class=hljs-params>()</span> </span>{</code><code class="hljs java">        <span class=hljs-keyword>final</span> <span class=hljs-keyword>int</span> _1MB = <span class=hljs-number>1024</span> * <span class=hljs-number>1024</span>;</code><code class=hljs> </code><code class="hljs python">        Field unsafeField = Unsafe.<span class=hljs-keyword>class</span>.getDeclaredFields()[<span class=hljs-number>0</span>];</code><code class="hljs lua">        unsafeField.setAccessible(<span class=hljs-literal>true</span>);</code><code class="hljs yaml">        <span class=hljs-string>Unsafe</span> <span class=hljs-string>unsafe</span> <span class=hljs-string>=</span> <span class=hljs-literal>null</span><span class=hljs-string>;</span></code><code class="hljs javascript">        <span class=hljs-keyword>try</span> {</code><code class="hljs kotlin">            unsafe = (Unsafe) unsafeField.<span class=hljs-keyword>get</span>(<span class=hljs-literal>null</span>);</code><code class="hljs javascript">        } <span class=hljs-keyword>catch</span> (IllegalArgumentException | IllegalAccessException e) {</code><code class="hljs css">            <span class=hljs-selector-tag>e</span><span class=hljs-selector-class>.printStackTrace</span>();</code><code class=hljs>        }</code><code class=hljs> </code><code class="hljs lua">        <span class=hljs-keyword>while</span> (<span class=hljs-literal>true</span>) {</code><code class="hljs rust">            <span class=hljs-keyword>unsafe</span>.allocateMemory(_1MB);</code><code class=hljs>        }</code><code class=hljs>    }</code><code class=hljs> </code><code class=hljs>    /**</code><code class="hljs markdown"><span class=hljs-bullet>     * </span>堆外内存OOM测试</code><code class=hljs>     */</code><code class="hljs java">    <span class=hljs-function><span class=hljs-keyword>public</span> <span class=hljs-keyword>void</span> <span class=hljs-title>directOOM</span><span class=hljs-params>()</span> </span>{</code><code class="hljs javascript">        OOMTest test = <span class=hljs-keyword>new</span> OOMTest();</code><code class="hljs css">        <span class=hljs-selector-tag>test</span><span class=hljs-selector-class>.allocDirectMemory</span>();</code><code class=hljs>    }</code><code class=hljs>}</code></pre></section><p> <br><section><section><section><span></span><span></span> </section><section><p><strong>4.1 openjdk 11.0.3 环境：+ G1回收</strong></section></section></section><p><br><p><strong>4.1.1 验证heap OOM </strong><p><br><p>把main方法中的test.heapOOM()行，注释打开，然后命令行下运行：<p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs css"><span class=hljs-selector-tag>java</span> <span class=hljs-selector-tag>-Xmx10M</span> <span class=hljs-selector-tag>-XX</span><span class=hljs-selector-pseudo>:+UseG1GC</span> <span class=hljs-selector-tag>-Xlog</span><span class=hljs-selector-pseudo>:gc</span>* <span class=hljs-selector-tag>-Xlog</span><span class=hljs-selector-pseudo>:gc</span><span class=hljs-selector-pseudo>:gc.log</span> <span class=hljs-selector-tag>-XX</span><span class=hljs-selector-pseudo>:+HeapDumpBeforeFullGC</span>  <span class=hljs-selector-tag>OOMTest</span><span class=hljs-selector-class>.java</span></code></pre></section></div></table><p>最后会输出：<br><p><br><section><ul><li><li><li><li><li><li><li><li><li><li></ul><pre class=code-snippet__js><code class="hljs markdown">[<span class=hljs-string>1.892s</span>][<span class=hljs-symbol>info</span>][<span class=hljs-string>gc             </span>] GC(42) Concurrent Cycle 228.393ms</code><code class="hljs lua">Exception <span class=hljs-keyword>in</span> thread <span class=hljs-string>&#34;main&#34;</span> java.lang.OutOfMemoryError: Java heap space</code><code class=hljs>        at java.base/java.util.Arrays.copyOf(Arrays.java:3689)</code><code class=hljs>        at java.base/java.util.ArrayList.grow(ArrayList.java:237)</code><code class=hljs>        at java.base/java.util.ArrayList.grow(ArrayList.java:242)</code><code class=hljs>        at java.base/java.util.ArrayList.add(ArrayList.java:485)</code><code class=hljs>        at java.base/java.util.ArrayList.add(ArrayList.java:498)</code><code class="hljs css">        <span class=hljs-selector-tag>at</span> <span class=hljs-selector-tag>oom</span><span class=hljs-selector-class>.OOMTest</span><span class=hljs-selector-class>.heapOOM</span>(<span class=hljs-selector-tag>OOMTest</span><span class=hljs-selector-class>.java</span><span class=hljs-selector-pseudo>:37)</span></code><code class="hljs css">        <span class=hljs-selector-tag>at</span> <span class=hljs-selector-tag>oom</span><span class=hljs-selector-class>.OOMTest</span><span class=hljs-selector-class>.main</span>(<span class=hljs-selector-tag>OOMTest</span><span class=hljs-selector-class>.java</span><span class=hljs-selector-pseudo>:16)</span></code><code class="hljs markdown">[<span class=hljs-string>1.895s</span>][<span class=hljs-symbol>info</span>][<span class=hljs-string>gc,heap,exit   </span>] Heap</code></pre></section><p><br><p>其中 OutOfMemoryError:Java heap space即表示heap OOM。<p><br><p><strong>4.1.2 验证stack溢出</strong><p><br><p>把main方法中的test.stackOverflow()行，注释打开，然后命令行下运行：<p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs css"><span class=hljs-selector-tag>java</span> <span class=hljs-selector-tag>-Xmx20M</span> <span class=hljs-selector-tag>-Xss180k</span> <span class=hljs-selector-tag>-XX</span><span class=hljs-selector-pseudo>:+UseG1GC</span> <span class=hljs-selector-tag>-Xlog</span><span class=hljs-selector-pseudo>:gc</span>* <span class=hljs-selector-tag>-Xlog</span><span class=hljs-selector-pseudo>:gc</span><span class=hljs-selector-pseudo>:gc.log</span>  <span class=hljs-selector-tag>-XX</span><span class=hljs-selector-pseudo>:+HeapDumpBeforeFullGC</span> <span class=hljs-selector-tag>OOMTest</span><span class=hljs-selector-class>.java</span></code></pre></section></div></table><p>最后会输出：<p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li><li><li><li><li><li></ul><pre class=code-snippet__js><code class="hljs markdown">[<span class=hljs-string>0.821s</span>][<span class=hljs-symbol>info</span>][<span class=hljs-string>gc           </span>] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 12M-&gt;7M(20M) 5.245ms</code><code class="hljs markdown">[<span class=hljs-string>0.821s</span>][<span class=hljs-symbol>info</span>][<span class=hljs-string>gc,cpu       </span>] GC(4) User=0.00s Sys=0.00s Real=0.00s</code><code class="hljs yaml"><span class=hljs-string>stack</span> <span class=hljs-attr>length:1699</span></code><code class="hljs lua">Exception <span class=hljs-keyword>in</span> thread <span class=hljs-string>&#34;main&#34;</span> java.lang.StackOverflowError</code><code class="hljs css">        <span class=hljs-selector-tag>at</span> <span class=hljs-selector-tag>oom</span><span class=hljs-selector-class>.OOMTest</span><span class=hljs-selector-class>.stackLeak</span>(<span class=hljs-selector-tag>OOMTest</span><span class=hljs-selector-class>.java</span><span class=hljs-selector-pseudo>:45)</span></code><code class="hljs css">        <span class=hljs-selector-tag>at</span> <span class=hljs-selector-tag>oom</span><span class=hljs-selector-class>.OOMTest</span><span class=hljs-selector-class>.stackLeak</span>(<span class=hljs-selector-tag>OOMTest</span><span class=hljs-selector-class>.java</span><span class=hljs-selector-pseudo>:45)</span></code></pre></section><p><br></div></table><p> 其中 StackOverflowError 即表示stack栈区内存不足，导致溢出。<p><br><p><strong>4.1.3 验证metaspace OOM</strong><p><br><p>把main方法中的test.metaspaceOOM()行，注释打开，然后命令行下运行：<p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs yaml"><span class=hljs-string>java</span> <span class=hljs-bullet>-Xmx20M</span> <span class=hljs-attr>-XX:MaxMetaspaceSize=10M</span> <span class=hljs-attr>-XX:+UseG1GC</span> <span class=hljs-attr>-Xlog:gc*</span>  <span class=hljs-attr>-Xlog:gc:gc.log</span> <span class=hljs-attr>-XX:+HeapDumpBeforeFullGC</span> <span class=hljs-string>OOMTest.java</span></code></pre></section></div></table><p><br><p>最后会输出：<p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li><li><li></ul><pre class=code-snippet__js><code class="hljs markdown">[<span class=hljs-string>0.582s</span>][<span class=hljs-symbol>info</span>][<span class=hljs-string>gc,metaspace,freelist,oom</span>]</code><code class="hljs lua">Exception <span class=hljs-keyword>in</span> thread <span class=hljs-string>&#34;main&#34;</span> java.lang.OutOfMemoryError: Metaspace</code><code class="hljs markdown">[<span class=hljs-string>0.584s</span>][<span class=hljs-symbol>info</span>][<span class=hljs-string>gc,heap,exit             </span>] Heap</code></pre></section></div></table><p> <p>其中 OutOfMemoryError: Metaspace 即表示Metaspace区OOM。<p><br><p><strong>4.1.4 验证堆外内存OOM</strong><p><br><p>把main方法中的test.directOOM()行，注释打开，然后命令行下运行：<p><br><p>最后会输出：<table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><p><br><section><ul><li><li><li><li></ul><pre class=code-snippet__js><code class="hljs markdown">[<span class=hljs-string>0.842s</span>][<span class=hljs-symbol>info</span>][<span class=hljs-string>gc,cpu       </span>] GC(4) User=0.06s Sys=0.00s Real=0.01s</code><code class="hljs lua">Exception <span class=hljs-keyword>in</span> thread <span class=hljs-string>&#34;main&#34;</span> java.lang.OutOfMemoryError</code><code class="hljs kotlin">        at java.base/jdk.<span class=hljs-keyword>internal</span>.misc.Unsafe.allocateMemory(Unsafe.java:<span class=hljs-number>616</span>)</code><code class=hljs>...</code></pre></section></div></table><p><br><p>其中OutOfMemoryError行并没有输出具体哪个区（注：堆外内存不属于JVM内存中的任何一个区，所以无法输出)，但紧接着有一行jdk.internal.misc.Unsafe.allocateMemory 可以看出是“堆外内存直接分配”导致的异常。<p> <section><section><section><span></span><span></span> </section><section><p><strong>4.2 openjdk 1.8.0_212 + CMS回收</strong></section></section></section><p><br><p>jdk1.8下，java命令无法直接运行.java文件，必须先编译，即：<p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs css"><span class=hljs-selector-tag>javac</span> <span class=hljs-selector-tag>OOMTest</span><span class=hljs-selector-class>.java</span> <span class=hljs-selector-tag>-encoding</span> <span class=hljs-selector-tag>utf-8</span></code></pre></section></div></table><p><br><p>（注：-encoding utf-8 是为了防止中文注释javac无法识别）成功后，会生成OOMTest.class文件， 然后再可以参考下面的命令进行测试。<p><br><p><strong>4.2.1 heap OOM测试：</strong><p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs yaml"><span class=hljs-string>java</span> <span class=hljs-bullet>-Xmx10M</span> <span class=hljs-attr>-XX:+UseConcMarkSweepGC</span> <span class=hljs-attr>-XX:+PrintGCDetails</span> <span class=hljs-attr>-XX:+PrintGCDateStamps</span> <span class=hljs-attr>-Xloggc:gc.log</span> <span class=hljs-attr>-XX:+HeapDumpOnOutOfMemoryError</span> <span class=hljs-string>OOMTest</span></code></pre></section></div></table><p><br><p><strong>4.2.2 验证stack溢出</strong><p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs yaml"><span class=hljs-string>java</span> <span class=hljs-bullet>-Xmx10M</span> <span class=hljs-bullet>-Xss128k</span> <span class=hljs-attr>-XX:+UseConcMarkSweepGC</span> <span class=hljs-attr>-XX:+PrintGCDetails</span> <span class=hljs-attr>-XX:+PrintGCDateStamps</span> <span class=hljs-attr>-Xloggc:gc.log</span> <span class=hljs-attr>-XX:+HeapDumpOnOutOfMemoryError</span> <span class=hljs-string>OOMTest</span></code></pre></section></div></table><p><br><p><strong>4.2.3 验证metaspace OOM</strong><p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs yaml"><span class=hljs-string>java</span> <span class=hljs-bullet>-Xmx20M</span> <span class=hljs-attr>-XX:+UseConcMarkSweepGC</span> <span class=hljs-attr>-XX:MaxMetaspaceSize=10M</span> <span class=hljs-attr>-XX:+PrintGCDetails</span> <span class=hljs-attr>-XX:+PrintGCDateStamps</span> <span class=hljs-attr>-Xloggc:gc.log</span> <span class=hljs-attr>-XX:+HeapDumpOnOutOfMemoryError</span> <span class=hljs-string>OOMTest</span></code></pre></section></div></table><p><br><p><strong>4.2.4 验证堆外内存OOM</strong><p><br><table width=654 height=NaN><tbody><tr><td width=1261 height=NaN><div><section><ul><li></ul><pre class=code-snippet__js><code class="hljs yaml"><span class=hljs-string>java</span> <span class=hljs-bullet>-Xmx20M</span> <span class=hljs-attr>-XX:+UseConcMarkSweepGC</span> <span class=hljs-attr>-XX:MaxDirectMemorySize=10M</span> <span class=hljs-attr>-XX:+PrintGCDetails</span> <span class=hljs-attr>-XX:+PrintGCDateStamps</span> <span class=hljs-attr>-Xloggc:gc.log</span> <span class=hljs-attr>-XX:+HeapDumpOnOutOfMemoryError</span> <span class=hljs-string>OOMTest</span></code></pre></section></div></table><p> <section><section><section><span></span><span></span> </section><section><p><strong>4.3 GC日志查看工具</strong></section></section></section><p><br><p>生成的gc日志文件，可以用开源工具<a href=https://github.com/chewiebug/GCViewer>GCViewer</a>查看，这是一个纯java写的GUI程序，使用很简单，File→Open File 选择gc日志文件即可。目前支持CMS/G1生成的日志文件，另外如果GC文件过大时，可能打不开。<p><br><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/0a0f64be data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTNkzzB6Ajysf9cficwQnNzIRC3akWIhdCKpBNMdCP5eketmwnqLIuHWHw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><img src=https://note-2019-images.oss-cn-hangzhou.aliyuncs.com/d7084d24 data-url="https://mmbiz.qpic.cn/mmbiz_png/kEeDgfCVf1coL0GOw5r448FhPHQ53vTN7YY16dnFD5lhgDAt5oYI2suEZYPwY8yUujlNuZjYSSsaOG89ee3VUA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" data-uploaded=true><p><br><p><span>GCViewer可以很方便的统计出GC的类型，次数，停顿时间，年青代/老年代的大小等，还有图表显示，非常方便。</span> <p><br><p><strong><span>参考文章：</span></strong><p><a href=https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5>https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5</a><p><a href=https://blog.csdn.net/heart_mine/article/details/79495032>https://blog.csdn.net/heart_mine/article/details/79495032</a><p><a href=https://www.programcreek.com/2013/04/jvm-run-time-data-areas/>https://www.programcreek.com/2013/04/jvm-run-time-data-areas/</a><p><a href=https://javapapers.com/core-java/java-jvm-run-time-data-areas/>https://javapapers.com/core-java/java-jvm-run-time-data-areas/</a><p><a href=https://javapapers.com/core-java/java-jvm-memory-types/>https://javapapers.com/core-java/java-jvm-memory-types/</a><p><a href=https://cloud.tencent.com/developer/article/1152616>https://cloud.tencent.com/developer/article/1152616</a><p><a href=https://www.jianshu.com/p/17e72bb01bf1>https://www.jianshu.com/p/17e72bb01bf1</a><p><a href=http://calvin1978.blogcn.com/articles/directbytebuffer.html>http://calvin1978.blogcn.com/articles/directbytebuffer.html</a><p><a href=https://www.cnkirito.moe/nio-buffer-recycle/>https://www.cnkirito.moe/nio-buffer-recycle/</a><p><a href=https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html>https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html</a><p><a href=http://inbravo.github.io/html/jvm.html>http://inbravo.github.io/html/jvm.html</a><p><a href=https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html>https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html</a><p><a href=https://segmentfault.com/a/1190000009783873>https://segmentfault.com/a/1190000009783873</a><p><a href=https://segmentfault.com/a/1190000016551339>https://segmentfault.com/a/1190000016551339</a><p><a href=https://www.team-bob.org/things-about-java-garbage-collection-1/2/>https://www.team-bob.org/things-about-java-garbage-collection-1/2/</a><p><a href=https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html>https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html</a><p><a href=https://tech.meituan.com/2016/09/23/g1.html>https://tech.meituan.com/2016/09/23/g1.html</a><p><a href="https://mp.weixin.qq.com/s?__biz=MzIzODYyNjkzNw==&amp;mid=2247483956&amp;idx=1&amp;sn=e9f8bbbb70c8919cf1efea2ed465f5d3&amp;scene=21#wechat_redirect">https://mp.weixin.qq.com/s/KUCs_BJUNfMMCO1T3_WAjw</a><p><a href=https://www.baeldung.com/jvm-zgc-garbage-collector>https://www.baeldung.com/jvm-zgc-garbage-collector</a><p><a href=http://xxfox.perfma.com/jvm/>http://xxfox.perfma.com/jvm/</a><p><a href=https://wiki.openjdk.java.net/display/zgc/Main>https://wiki.openjdk.java.net/display/zgc/Main</a><p><a href=http://cr.openjdk.java.net/~pliden/slides/ZGC-FOSDEM-2018.pdf>http://cr.openjdk.java.net/~pliden/slides/ZGC-FOSDEM-2018.pdf</a><p><a href=http://www.ishenping.com/ArtInfo/43701.html>http://www.ishenping.com/ArtInfo/43701.html</a><p><a href=http://likehui.top/2019/04/11/ZGC-%E7%89%B9%E6%80%A7%E8%A7%A3%E8%AF%BB/>http://likehui.top/2019/04/11/ZGC-%E7%89%B9%E6%80%A7%E8%A7%A3%E8%AF%BB/</a><p><br><p><strong>【推荐阅读】</strong><p><strong><br></strong><ul><li><p><a href="http://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&amp;mid=2697268816&amp;idx=1&amp;sn=17a0aa1d1dc5adf1e65642bc5088701c&amp;chksm=8376f164b401787290471abaf39a473daee8319c619c1736979707be69ea429aa6086406f621&amp;scene=21#wechat_redirect">加载速度提升15%，携程对RN新一代JS引擎Hermes的调研</a><li><p><a href="http://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&amp;mid=2697268744&amp;idx=1&amp;sn=8e1062d742796661d6659da28d6741e5&amp;chksm=8376f13cb401782add4058755238660377d93b89d3e0321e0752dc494fa8ba7936b2654f5467&amp;scene=21#wechat_redirect">携程酒店RSocket实践</a><li><p><a href="http://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&amp;mid=2697268744&amp;idx=2&amp;sn=a0096df92b0ca2d25660c1110e0a9a07&amp;chksm=8376f13cb401782a27f3155b4bfe087d3758313d5b6caad34384eeb771353bbf241c3e36b06a&amp;scene=21#wechat_redirect">携程酒店MOCK全链路实践</a><li><p><a href="http://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&amp;mid=2697268699&amp;idx=1&amp;sn=d761aa9f77b5a6acd7e26134169c13ae&amp;chksm=8376f2efb4017bf9777495d5d4cba16cea02a87b608e9c63f2ca1b6817cdba3dd84dfaadb20d&amp;scene=21#wechat_redirect">万字长文全面解析GraphQL，携程微服务背景下的前后端数据交互方案</a><li><p><a href="http://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&amp;mid=2697268699&amp;idx=2&amp;sn=043691207669c176562de2a1785917e5&amp;chksm=8376f2efb4017bf98ffbe3a4f7529b807084bb5258d6476d4743ea5af0204122dd5ee8ea546f&amp;scene=21#wechat_redirect">单个场景秒级返回，携程机票持续集成之线上场景回放优化</a><li><p><a href="http://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&amp;mid=2697268683&amp;idx=1&amp;sn=c620707d3c0a137ecf91e3871da59d9f&amp;chksm=8376f2ffb4017be93da5948acb4f49f6eb5bb69930c13b65e3c17efa6209d76dcd325d90a371&amp;scene=21#wechat_redirect">跨多业务线挑战下，携程订单索引服务的1.0到2.0</a></ul><p><br><p><p><br></div></div></div></div></div></div><hr><div><label>原网址: <a href=https://mp.weixin.qq.com/s/9xGsz5TpTSN0LxeOdNV8zA>访问</a></label><br><label>创建于: 2019-08-30 11:08:26</label><br><label>目录: default</label><br><label>标签: <code>java</code>, <code>gc</code></label></div></div>